/** 
 * @file: mlib_key_c.c
 * @author: chen <ischen.x@outlook.com>
 * @brief: This is a key scanning driver library that supports single-click, double-click, 
 *         multiple-click, and long-press events for scanning multiple keys. It is very 
 *         easy to use, consumes minimal resources, and is well-suited for use in embedded
 *         development with this library.
 *         You can define the macro multi_click_enable to enable the detection of multiple-click 
 *         events (enabled by default), or leave it undefined to disable it and reduce resource 
 *         usage. Additionally, you can increase the sensitivity of single-click event scanning.
 * @data: 2024/02/02
 * @version: V1.0
 */
#include "mlib_key.h"

static Button *button_header = NULL;
static uint8_t (*btn_get_gpio_input_fun)(uint8_t btn_id) = NULL;

/** 
 * @brief Set call back function for read button gpio input
 * @param func: gpio input function pointer
 * @retval None
 */
void btn_set_get_gpio_input_fun(uint8_t (*func)(uint8_t))
{
	btn_get_gpio_input_fun = func;
}

/** 
 * @brief Set the callback function for event triggering.
 * @retval None
 */
void btn_set_callback(Button *btn, BtnEvent evt, BtnCallback cbFunc)
{
    btn->cb[evt] = cbFunc;
}

/** 
 * @brief Initialize a key
 * @param btn: Button class pointer
 * @param btn_id: Button 
 * @param btn_trigger_status: Trigger level of the key
 * @retval: None
 */
void btn_init(Button *btn, uint8_t btn_id, uint8_t btn_trigger_status)
{
    if(btn != NULL){
        btn->trigger_status = btn_trigger_status;
        btn->id = btn_id;
        btn->event = NONE_PRESS;
        btn->last_trigger_ticks = 0;
		btn->trigger_keep_ticks = 0;
#ifdef multi_click_enable
		btn->release_ticks = 0;
		btn->trigger_times = 0;
#endif
		btn->next_button = button_header;
		button_header = btn;
    }
}

#ifdef multi_click_enable
/**
 * @brief Check for key events and execute the event callback function.
 * @param btn: Pointer to the button.
 * @retval: None
 */
static void btn_event_check_and_call_cb(Button *btn)
{
    uint8_t have_event = 0;
	if(btn->trigger_times){					/* short trigger/muti trigger */
		have_event = 1;
		switch(btn->trigger_times){
			case 1:
				btn->event = SINGLE_CLICK;
				break;
			case 2:
				btn->event = DOUBLE_CLICK;
				break;
			default:
				btn->event = MULTI_CLICK;
				break;
		}
	}else if(btn->trigger_keep_ticks >= mlib_ms_to_ticks(long_press_threshold_ms)){
		have_event = 1;
		btn->event = LONG_PRESS;
	}
    if(have_event){
		if(btn->event != NONE_PRESS){
			if(btn->cb[btn->event] != NULL){
				btn->cb[btn->event](btn);
				btn->event = NONE_PRESS;
			}
		}		
	}
}

/**
 * @brief Key scanning main task.Please call it once within a ticks interval.
          If you place it in a hard interrupt service routine, please avoid 
		  using blocking tasks in the key event callback function.
 * @param None.
 * @retval: None.
 */
void button_scan(void)
{
	static volatile uint32_t btn_ticks = 0;
    Button *p_btn = button_header;
    btn_ticks++;
    while(p_btn){
        if(btn_get_gpio_input_fun(p_btn->id) == p_btn->trigger_status){				/* pressed */
            if(btn_ticks == p_btn->last_trigger_ticks + 1){          					/* continuity or not(debounce) */
				p_btn->trigger_keep_ticks++;											
				if(p_btn->trigger_keep_ticks >= mlib_ms_to_ticks(debouncing_time) ){		/* continuous/short trigger */
					if(p_btn->trigger_keep_ticks <= mlib_ms_to_ticks(long_press_threshold_ms)){
						if(p_btn->release_ticks != 0){
							p_btn->release_ticks = 0;
							p_btn->trigger_times++;
						}
					}else{
						p_btn->trigger_times = 0;											/* long trigger */
						btn_event_check_and_call_cb(p_btn);
					}
				}		
			}else{																		/* debounce */
				p_btn->trigger_keep_ticks = 0;
			}
			p_btn->last_trigger_ticks = btn_ticks;
        }else{																		/* release */
			p_btn->release_ticks++;
			if(p_btn->release_ticks > mlib_ms_to_ticks(time_between_multiple_clicks)){											/* event stop */
				btn_event_check_and_call_cb(p_btn);										/* check event */
				p_btn->release_ticks = 0;
				p_btn->trigger_times = 0;
			}
			p_btn->trigger_keep_ticks = 0;
		}
		p_btn = p_btn->next_button;
    }
}
#else
/**
 * @brief Call the event callback function.
 * @param btn: Pointer to the button.
 * @retval: None
 */
static void btn_event_call(Button *btn)
{
    if(btn->event != NONE_PRESS){
        if(btn->cb[btn->event] != NULL){
            btn->cb[btn->event](btn);
            btn->event = NONE_PRESS;
        }
    }
}

/**
 * @brief Key scanning main task.Please call it once within a ticks interval.
          If you place it in a hard interrupt service routine, please avoid 
		  using blocking tasks in the key event callback function.
 * @param None.
 * @retval: None.
 */
void button_scan(void)
{
    static volatile uint32_t btn_ticks = 0;
    Button *p_btn = button_header;
    btn_ticks++;
    while(p_btn){
        if(btn_get_gpio_input_fun(p_btn->id) == p_btn->trigger_status){			/* pressed */
            if(btn_ticks == p_btn->last_trigger_ticks + 1){          				/* continuity or not(debounce) */
                p_btn->trigger_keep_ticks++;											/* continuous */
                if(p_btn->trigger_keep_ticks >= mlib_ms_to_ticks(long_press_threshold_ms)){
                    p_btn->event = LONG_PRESS;
                    btn_event_call(p_btn);
                }
            }else{																		/* debounce */
                p_btn->trigger_keep_ticks = 0;
            }
            p_btn->last_trigger_ticks = btn_ticks;
        }else{																	/* unpressed */
            if(p_btn->trigger_keep_ticks >= mlib_ms_to_ticks(debouncing_time)           /* check event */
               && p_btn->trigger_keep_ticks < mlib_ms_to_ticks(long_press_threshold_ms))
            {             
                p_btn->event = SINGLE_CLICK;
                btn_event_call(p_btn);	
            }							             			
            p_btn->trigger_keep_ticks = 0;
        }
        p_btn = p_btn->next_button;
    }
}
#endif
